package com.tjetc;

import com.sun.source.tree.Scope;
import com.tjetc.annotation.Autowired;
import com.tjetc.annotation.Component;
import com.tjetc.annotation.Controller;
import com.tjetc.annotation.Service;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 依赖web.xml
 *
 * @author gaoyongxiang
 * @version v1.0
 * @date 2025/9/8
 */
@SuppressWarnings("all")
public class MyWebApplicationContext {

    // 这里我们把获取类路径和根据类路径生成对应的实体类分开
    // 保存所有的类路径集合
    private List<String> fullPathList = new ArrayList<>();


    // ioc容器
    private ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();


    // 存放xml配置文件
    private String configLocation;

    // 通过构造器将配置文件传入
    public MyWebApplicationContext(String configLocation) {
        this.configLocation = configLocation;
        // 这里就要开始执行容器的初始化
        init();
    }

    // 容器的初始化方法
    private void init() {
//        System.out.println("容器的初始化");
        // 这套流程和我们之前学习的Spring容器的初始化几乎一样
        // 得到配置文件中的扫描路径

        // 处理要扫描的文件路径 classpath:mySpringMVC.xml
        configLocation = configLocation.split(":")[1];
        // 解析xml文件 拿到基础路径
        String basePackage = getScanPath(configLocation);
        // 根据逗号 可以得到所有要扫描的包路径
        String[] basePackages = basePackage.split(",");
        // 遍历包并执行扫描
        if (basePackages.length > 0) {
            for (String pack : basePackages) {
                // 封装一个扫描方法
                scanPackage(pack);
            }
        }
        // 此时扫描的结果
//        System.out.println("fullPathList = " + fullPathList);

        // 根据扫描的所有路径生成对应的实例对象放到ioc容器中
        executeInstance();

//        System.out.println("ioc = " + ioc);

        // 进行自动注入
        executeAutowired();

//        System.out.println("ioc = " + ioc);


    }

    // 执行自动注入
    private void executeAutowired() {
        // 如果ioc中没有bean 就不需要执行自动注入
        if (ioc.isEmpty()) {
            return;
        }

        // 遍历ioc容器执行 自动注入
        ioc.forEach((k, v) -> {
            // 拿到当前bean中所有的字段
            Field[] declaredFields = v.getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
//                System.out.println("declaredField = " + declaredField);
                // 判断该属性是否存在Autowired注解
                if (declaredField.isAnnotationPresent(Autowired.class)) {
                    Autowired autowired = (Autowired) declaredField.getAnnotation(Autowired.class);
                    String beanName = autowired.value();
                    // 如果没有注解写value那么就按照默认机制进行注入
                    if (StringUtils.isBlank(beanName)) {
                        // 得到字段类型的首字母小写 作为名字进行装配
                        Class<?> type = declaredField.getType();
//                        System.out.println("type = " + type.getName());
                        // 处理type信息
                        String[] typeNames = type.getName().split("\\.");
                        String typeName = typeNames[typeNames.length - 1];
                        beanName = StringUtils.uncapitalize(typeName);
//                        System.out.println("beanName = " + beanName);
                    }
                    // 从ioc容器中根据beanName找到对应的bean对象
                    Object o = ioc.get(beanName);
                    if (Objects.isNull(o)) {
                        // 抛出异常 自动注入失败
                        throw new RuntimeException("ioc中不存在此bean");
                    }

                    // 进行注入 instance.setXXX(bean);
                    // 注意：因为是private方法 所以需要暴力破解 但是注意破解设置完毕后 要恢复
                    declaredField.setAccessible(true);
                    try {
                        declaredField.set(v, o);
                    } catch (Exception e) {

                    }
                    declaredField.setAccessible(false);
                }
            }
        });

    }

    // 执行实例化操作
    private void executeInstance() {
        // 如果扫描的类一个都没有 那么就不需要实例化了
        if (fullPathList.size() == 0) {
            return;
        }
        // 通过类路径实现反射创建实例对象
        try {
            for (String fullPath : fullPathList) {
//                System.out.println("fullPath = " + fullPath);
                // 可以拿到类名
                String[] classNames = fullPath.split("\\.");
//                System.out.println(Arrays.toString(classNames));
                String className = classNames[classNames.length - 1];
//                System.out.println("className = " + className);
                Class<?> clazz = Class.forName(fullPath);
                // 判断是否有Controller注解或者是Service注解
                if (clazz.isAnnotationPresent(Controller.class)) {
                    // 4. 获取beanName 并放入集合中
                    // 获取Controller注解
                    Controller controller = (Controller) clazz.getDeclaredAnnotation(Controller.class);
                    // 获取Controller注解中的value值
                    String beanName = controller.value();
                    if (StringUtils.isBlank(beanName)) {
                        // 如果为空 则用当前类名的首字母小写进行创建
                        beanName = StringUtils.uncapitalize(className);
                    }
                    ioc.put(beanName, clazz.getDeclaredConstructor().newInstance());
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    // Service是根据接口注入的
                    Service service = (Service) clazz.getDeclaredAnnotation(Service.class);
                    String beanName = service.value();

                    if (StringUtils.isBlank(beanName)) {
                        // 可以根据接口名/类名进行注入
                        // 通过反射得到所有接口的名称
                        Class<?>[] interfaces = clazz.getInterfaces();
                        // 直接注入
                        for (Class<?> anInterface : interfaces) {
                            // 处理一下所有类名
                            String[] interfaceName = anInterface.getName().split("\\.");
                            String className1 = interfaceName[interfaceName.length - 1];
                            String beanName1 = StringUtils.uncapitalize(className1);
                            ioc.put(beanName1, clazz.getDeclaredConstructor().newInstance());
                        }
                        // 还可以根据当前类名的首字母小写进行注入
                        beanName = StringUtils.uncapitalize(className);
                        ioc.put(beanName, clazz.getDeclaredConstructor().newInstance());
                    } else {
                        // 按照指定名称进行注入
                        ioc.put(beanName, clazz.getDeclaredConstructor().newInstance());
                    }

                }
            }
        } catch (Exception e) {

        }

    }

    // 扫描包 com.tjetc.controller
    private void scanPackage(String pack) {
        // System.out.println(pack);
        // (1) 先获取app类加载器
        ClassLoader classLoader = MyWebApplicationContext.class.getClassLoader();
        // (2) 通过类加载器找到所有类的url路径
        // 注意：包路径是/  而我们获取到路径是. 所以我们需要转换
        pack = StringUtils.replaceChars(pack, ".", "/");
//        System.out.println(pack);
        URL url = classLoader.getResource(pack);
//        System.out.println(url);
        File file = new File(url.getPath());
        // 拿到该目录下的所有文件
        File[] files = file.listFiles();
        for (File f : files) {
            // 如果还是一个文件夹 那么我们需要递归扫描子目录中的文件
            if (f.isDirectory()) {
                scanPackage(pack + "." + f.getName());
            } else {
                // 类路径 com.tjetc.UserController
                // 拼接之前需要将pack替换成包路径
                pack = StringUtils.replaceChars(pack, "/", ".");
                String fullPath = pack + "." + f.getName().replaceAll(".class", "");
//                System.out.println(fullPath);
                fullPathList.add(fullPath);
            }
        }
    }

    // 根据配置xml文件 得到扫描的基础路径
    private String getScanPath(String configLocation) {
        SAXReader saxReader = new SAXReader();
        InputStream in = SAXReader.class.getClassLoader().getResourceAsStream(configLocation);
        try {
            Document document = saxReader.read(in);
            Element rootElement = document.getRootElement();
//            System.out.println(rootElement);
            // 得到component-scan元素
            Element componentScanElement = rootElement.element("component-scan");
//            System.out.println(componentScanElement);
            return componentScanElement.attribute("base-package").getText();
        } catch (Exception e) {

        }
        return "";
    }


    public ConcurrentHashMap<String, Object> getIoc() {
        return ioc;
    }
}